package com.zmj.sy.mom.srv.aps.service.scheduler.inverted;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.lang.Assert;
import com.zmj.sy.mom.srv.aps.bean.entity.order.*;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Group;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Shift;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Station;
import com.zmj.sy.mom.srv.aps.mapper.*;
import com.zmj.sy.mom.srv.aps.service.scheduler.Scheduler;
import com.zmj.sy.mom.srv.aps.service.scheduler.extractor.CalendarExtractor;
import com.zmj.sy.mom.srv.aps.service.scheduler.extractor.FactoryExtractor;
import com.zmj.sy.mom.srv.aps.service.scheduler.extractor.OrderExtractor;
import com.zmj.sy.mom.srv.aps.service.scheduler.order.DefaultBom;
import com.zmj.sy.mom.srv.aps.service.scheduler.order.DefaultOrder;
import com.zmj.sy.mom.srv.aps.service.scheduler.order.DefaultPhase;
import com.zmj.sy.mom.srv.aps.service.scheduler.resource.shift.MultiTimeRange;
import com.zmj.sy.mom.srv.aps.service.scheduler.schedule.*;
import com.zmj.sy.mom.srv.aps.utils.MkBeanUtils;
import com.zmj.sy.mom.srv.aps.utils.SyExceptionUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 倒排排产
 */
@Component
@RequiredArgsConstructor
public class InvertedScheduler implements Scheduler<InvertedScheduleOrder, InvertedScheduleResource, InvertedScheduleContext, InvertedSchedule> {

    private final StringRedisTemplate stringRedisTemplate;

    private final OrderExtractor orderLoader;

    private final FactoryExtractor factoryLoader;

    private final CalendarExtractor calendarLoader;

    private final ApsBatchMapper apsBatchMapper;

    private final ApsWorkOrderMapper apsWorkOrderMapper;

    private final ApsWorkDetailMapper apsWorkDetailMapper;

    private final ApsDetailWeekdayMapper apsDetailWeekdayMapper;

    private final OrderMapper orderMapper;

    /**
     * 混合排产
     */
    @Override
    public InvertedSchedule schedule(InvertedScheduleOrder order,
                                     InvertedScheduleResource resource,
                                     InvertedScheduleContext context) {
        InvertedSchedule schedule = new InvertedSchedule();
        schedule.setOrders(new ArrayList<>());
        DefaultOrder previousOrder = null;
        List<Plan> previousDeliveryPlans = new ArrayList<>();
        DefaultOrder currentOrder = null;
        while ((currentOrder = order.nextOrder(context, currentOrder)) != null) {
            // 计算首批交付时间
            LocalDateTime firstBatchDeliveryTime;
            if (previousOrder == null) {
                firstBatchDeliveryTime = context.getReferenceTime();
            } else {
                if (Objects.equals(currentOrder.getOrder().getConstructNo(), previousOrder.getOrder().getConstructNo())) {
                    firstBatchDeliveryTime = previousDeliveryPlans.get(0).getTime();
                } else {
                    firstBatchDeliveryTime = previousDeliveryPlans.get(previousDeliveryPlans.size() - 1).getTime().plusDays(1);
                }
            }
            List<Plan> deliveryPlans = new ArrayList<>();
            for (int batchIndex = 0; batchIndex < currentOrder.getReverseOrders().size(); batchIndex++) {
                Plan plan = new Plan();
                plan.setTime(firstBatchDeliveryTime.plusDays(batchIndex));
                plan.setCount(BigDecimal.valueOf(currentOrder.getReverseOrders().get(batchIndex).getOrderQty()));
                deliveryPlans.add(plan);
            }
            DefaultOrderSchedule orderSchedule = schedule(resource, currentOrder, deliveryPlans);
            schedule.getOrders().add(orderSchedule);
            previousOrder = currentOrder;
            previousDeliveryPlans = deliveryPlans;
        }
        return schedule;
    }

    /**
     * 单订单排产
     */
    private DefaultOrderSchedule schedule(InvertedScheduleResource resource,
                                          DefaultOrder order,
                                          List<Plan> deliveryPlans) {
        DefaultOrderSchedule orderSchedule = new DefaultOrderSchedule();
        orderSchedule.setOrder(order);
        orderSchedule.setBomSchedules(new ArrayList<>());
        Stack<DefaultBom> bomStack = new Stack<>();
        Stack<List<Plan>> deliveryPlanStack = new Stack<>();
        Map<Integer, DefaultBomSchedule> bomScheduleMap = new HashMap<>();

        for (DefaultBom root : order.getRoots()) {
            bomStack.push(root);
            // 订单交付计划转换为物料交付计划
            deliveryPlanStack.push(
                    deliveryPlans.stream()
                            .map(plan -> {
                                Plan bomPlan = new Plan();
                                bomPlan.setTime(plan.getTime());
                                bomPlan.setCount(plan.getCount().multiply(root.getBom().getTotalCount()));
                                return bomPlan;
                            })
                            .collect(Collectors.toList())
            );
        }
        while (!bomStack.isEmpty()) {
            DefaultBom bom = bomStack.pop();
            List<Plan> bomDeliveryPlans = deliveryPlanStack.pop();

            DefaultBomSchedule bomSchedule = schedule(resource, bom, bomDeliveryPlans);
            bomSchedule.setOrderSchedule(orderSchedule);
            bomSchedule.setChildren(new ArrayList<>());

            int pid = bom.getBom().getPid() != null ? bom.getBom().getPid() : 0;
            DefaultBomSchedule parent = bomScheduleMap.get(pid);
            if (parent != null) {
                parent.getChildren().add(bomSchedule);
                bomSchedule.setParent(parent);
            }

            orderSchedule.getBomSchedules().add(bomSchedule);
            bomScheduleMap.put(bom.getBom().getId(), bomSchedule);

            List<Plan> bomStartPlans = bomDeliveryPlans;
            if (!bom.getPhases().isEmpty()) {
                DefaultPhase startPhase = bom.getPhases().get(0);
                bomStartPlans = bomSchedule.getPhaseSchedules().stream()
                        .filter(phaseSchedule -> phaseSchedule.getPhase().equals(startPhase))
                        .map(phaseSchedule -> {
                            Plan plan = new Plan();
                            plan.setTime(phaseSchedule.getPlanStartDateTime());
                            plan.setCount(phaseSchedule.getTotalCount());
                            return plan;
                        })
                        .collect(Collectors.toList());
            }
            for (DefaultBom child : bom.getChildren()) {
                bomStack.push(child);
                deliveryPlanStack.push(
                        bomStartPlans.stream()
                                .map(parentPlan -> {
                                    Plan plan = new Plan();
                                    plan.setTime(parentPlan.getTime());
                                    BigDecimal assembleCount = child.getBom().getTotalCount()
                                            .divide(bom.getBom().getTotalCount(), 0, RoundingMode.HALF_UP);
                                    plan.setCount(parentPlan.getCount().multiply(assembleCount));
                                    return plan;
                                })
                                .collect(Collectors.toList())
                );
            }
        }
        return orderSchedule;
    }

    private DefaultBomSchedule schedule(InvertedScheduleResource resource,
                                        DefaultBom bom,
                                        List<Plan> bomDeliveryPlans) {
        DefaultBomSchedule bomSchedule = new DefaultBomSchedule();
        bomSchedule.setBom(bom);
        bomSchedule.setPhaseSchedules(new ArrayList<>());

        List<Plan>  phaseDeliveryPlans = bomDeliveryPlans;
        for (int phaseIndex = bom.getPhases().size() - 1; phaseIndex > -1; phaseIndex--) {
            List<DefaultPhaseSchedule> phaseSchedules = schedule(resource, bom.getPhases().get(phaseIndex), phaseDeliveryPlans);

            phaseSchedules.forEach(phaseSchedule -> phaseSchedule.setBomSchedule(bomSchedule));

            bomSchedule.getPhaseSchedules().addAll(phaseSchedules);
            // 计算下道工序交付计划，等于当道工序的开始时间
            phaseDeliveryPlans = phaseSchedules.stream()
                    .map(workDetailSchedule -> {
                        Plan plan = new Plan();
                        plan.setTime(workDetailSchedule.getPlanStartDateTime());
                        plan.setCount(workDetailSchedule.getTotalCount());
                        return plan;
                    }).collect(Collectors.toList());
        }

        LocalDateTime bomStartTime = null;
        LocalDateTime bomEndTime = null;
        Duration bomExecuteTime = Duration.ZERO;
        for (DefaultPhaseSchedule phaseSchedule : bomSchedule.getPhaseSchedules()) {
            if (bomStartTime == null) {
                bomStartTime = phaseSchedule.getPlanStartDateTime();
            } else if (bomStartTime.isAfter(phaseSchedule.getPlanStartDateTime())) {
                bomStartTime = phaseSchedule.getPlanStartDateTime();
            }
            if (bomEndTime == null) {
                bomEndTime = phaseSchedule.getPlanEndDateTime();
            } else if (bomEndTime.isBefore(phaseSchedule.getPlanEndDateTime())) {
                bomEndTime = phaseSchedule.getPlanEndDateTime();
            }
            bomExecuteTime = bomExecuteTime.plus(phaseSchedule.getExecuteTime());
        }
        if (bomStartTime == null) {
            bomStartTime = bomDeliveryPlans.get(0).getTime();
            bomExecuteTime = Duration.ZERO;
            bomEndTime = bomDeliveryPlans.get(bomDeliveryPlans.size() -1).getTime();
        }
        bomSchedule.setPlanStartDateTime(bomStartTime);
        bomSchedule.setPlanEndDateTime(bomEndTime);
        bomSchedule.setExecuteTime(bomExecuteTime);

        return bomSchedule;
    }

    private List<DefaultPhaseSchedule> schedule(InvertedScheduleResource resource,
                                                DefaultPhase phase,
                                                List<Plan> deliveryPlans) {
        // 日产量，由拆包定义
        BigDecimal dailyProduction = phase.getBom().getBom().getTotalCount()
                .multiply(BigDecimal.valueOf(phase.getBom().getOrder().getReverseOrders().get(0).getOrderQty()));
        // 计划总交付数
        BigDecimal totalPlanDeliveryCount = deliveryPlans.stream()
                .map(Plan::getCount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // 日总班次数，每班产量相同
        int totalShiftCount = resource.shifts(phase).size();
        // 从首批交付时间开始，以最大生产力开始生产，直到计划总交付数完成，如果无法完成则进行提前，最多提前365天
        int days = 365;
        for (int advanceDays = 0; advanceDays < days; advanceDays++) {
            LocalDateTime firstBatchDeliveryTime = deliveryPlans.get(0).getTime().minusDays(advanceDays);
            List<DefaultPhaseSchedule> phaseSchedule = new ArrayList<>();
            BigDecimal deliveredCount = BigDecimal.ZERO;
            // 逐天往后安排生产
            for (int productDayIndex = 0; productDayIndex < days; productDayIndex++) {
                LocalDateTime deliveryTime = firstBatchDeliveryTime.plusDays(productDayIndex);
                LocalDate deliveryDate = deliveryTime.minusHours(6).toLocalDate();
                // 应交付数
                BigDecimal planDeliveryCount = deliveryPlans.stream()
                        .filter(plan -> !plan.getTime().isAfter(deliveryTime))
                        .map(Plan::getCount)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                // 当天应生产数量 = 当天应交付数 - 已交付数
                BigDecimal planProductionCount = planDeliveryCount.subtract(deliveredCount);

                // 当天可生产数 = 日产量 *  可用工时 / 一天总工时
                int workShiftCount = resource.workShifts(phase, deliveryDate).size();
                BigDecimal productionCount = dailyProduction
                        .multiply(BigDecimal.valueOf(workShiftCount))
                        .divide(BigDecimal.valueOf(totalShiftCount), 0, RoundingMode.UP);

                // 当天可生产数必须不小于当天计划生产数，否则提前一天
                if (productionCount.compareTo(planProductionCount) < 0) {
                    break;
                }

                // 剩余需生产数
                BigDecimal remainPlanProductionCount = totalPlanDeliveryCount.subtract(deliveredCount);

                // 当前生产数 = 当天可生产数和剩余生产数的较小值
                BigDecimal production = remainPlanProductionCount.compareTo(productionCount) < 0
                        ? remainPlanProductionCount : productionCount;

                if (production.compareTo(BigDecimal.ZERO) > 0) {
                    phaseSchedule.add(schedule(resource, phase, deliveryTime, production));
                }

                deliveredCount = deliveredCount.add(production);
                if (deliveredCount.compareTo(totalPlanDeliveryCount) >= 0) {
                    break;
                }
            }
            if (deliveredCount.compareTo(totalPlanDeliveryCount) < 0) {
                continue;
            }
            return phaseSchedule;
        }
        throw SyExceptionUtils.e("数据异常，无法完成计划");
    }

    private DefaultPhaseSchedule schedule(InvertedScheduleResource resource,
                                          DefaultPhase phase,
                                          LocalDateTime deliveryTime,
                                          BigDecimal production) {
        DefaultPhaseSchedule phaseSchedule = new DefaultPhaseSchedule();
        phaseSchedule.setPhase(phase);
        phaseSchedule.setTotalCount(production);

        Duration executeTime = InvertedScheduleOrder.executeTime(resource, phase);
        List<DefaultShiftSchedule> workTimes;
        LocalDateTime phaseStartTime;
        LocalDateTime phaseEndTime;
        if (executeTime.compareTo(Duration.ofSeconds(2)) < 0) {
            workTimes = new ArrayList<>();
            phaseStartTime = deliveryTime;
            phaseEndTime = deliveryTime;
        } else {
            workTimes = schedule(resource, phase, executeTime, deliveryTime);
            workTimes.forEach(workTime -> workTime.setPhaseSchedule(phaseSchedule));
            phaseStartTime = workTimes.get(0).getPlanStartDateTime();
            phaseEndTime = workTimes.get(workTimes.size() - 1).getPlanEndDateTime();
        }
        phaseSchedule.setShiftSchedules(workTimes);
        phaseSchedule.setPlanStartDateTime(phaseStartTime);
        phaseSchedule.setPlanEndDateTime(phaseEndTime);
        phaseSchedule.setExecuteTime(executeTime);

        String workGroupNumber = phase.getPhase().getWorkGroupNumber();
        Group group = resource.getFactory().group(workGroupNumber);
        Assert.notNull(group, "工作组" + workGroupNumber + "不存在");
        List<Station> stations = resource.getFactory().stations(group.getId());
        Assert.notEmpty(stations, "工作组" + workGroupNumber + "没有工位");
        Station station = resource.getFactory().station(stations.get(0).getId());
        phaseSchedule.setGroup(group);
        phaseSchedule.setStation(station);
        return phaseSchedule;
    }

    private List<DefaultShiftSchedule> schedule(InvertedScheduleResource resource,
                                                DefaultPhase phase,
                                                Duration executeTime,
                                                LocalDateTime deliveryTime) {
        List<DefaultShiftSchedule> shiftSchedules = new ArrayList<>();
        int days = 365;
        List<Shift> shifts = resource.shifts(phase);
        Duration totalExecuteTime = Duration.ZERO;
        for (int productDayIndex = 0; productDayIndex < days; productDayIndex++) {
            LocalDate date = deliveryTime.minusDays(productDayIndex).minusHours(6).toLocalDate();
            // 当天工作班次，仅用作判断当天是否休息
            List<Shift> workShifts = resource.workShifts(phase, date);
            if (workShifts.isEmpty()) {
                continue;
            }
            for (int shiftIndex = shifts.size() - 1; shiftIndex > -1; shiftIndex--) {
                Shift shift = shifts.get(shiftIndex);
                MultiTimeRange workTime = resource.workTime(phase, date, shift);
                if (workTime.isEmpty()) {
                    continue;
                }
                Duration remainingExecuteTime = executeTime.minus(totalExecuteTime);
                workTime = workTime.lengthBefore(remainingExecuteTime, deliveryTime);
                if (!workTime.isEmpty()) {
                    DefaultShiftSchedule shiftSchedule = new DefaultShiftSchedule();
                    shiftSchedule.setDate(date);
                    shiftSchedule.setShift(shifts.get(shiftIndex));
                    shiftSchedule.setPlanStartDateTime(workTime.getStartTime());
                    shiftSchedule.setPlanEndDateTime(workTime.getEndTime());
                    shiftSchedule.setExecuteTime(workTime.length());
                    shiftSchedules.add(shiftSchedule);
                    totalExecuteTime = totalExecuteTime.plus(workTime.length());
                }
                if (totalExecuteTime.compareTo(executeTime) >= 0) {
                    break;
                }
            }
        }
        Assert.isTrue(totalExecuteTime.compareTo(executeTime) >= 0, "日历异常，无法完成计划");
        shiftSchedules.sort(Comparator.comparing(DefaultShiftSchedule::getPlanStartDateTime));
        return shiftSchedules;
    }

    public void schedule(InvertedScheduleContext context, List<Integer> orderIdList) {
        InvertedScheduleOrder order = loadOrder(context, new HashSet<>(orderIdList));
        InvertedScheduleResource resource = loadResource(context, order);
        InvertedSchedule schedule = schedule(order, resource, context);
        lock(context, order);
        try {
            clearSchedule(order);
            saveSchedule(schedule);
        } finally {
            unlock(context);
        }
    }

    /**
     * 加载订单相关数据
     */
    private InvertedScheduleOrder loadOrder(InvertedScheduleContext context, Set<Integer> orderIds) {
        List<DefaultOrder> orders = orderLoader.load(orderIds);
        InvertedScheduleOrder scheduleOrder = new InvertedScheduleOrder();
        scheduleOrder.setOrders(orders);
        return scheduleOrder;
    }

    /**
     * 加载资源相关数据
     */
    public InvertedScheduleResource loadResource(InvertedScheduleContext context, InvertedScheduleOrder order) {
        InvertedScheduleResource resource = new InvertedScheduleResource();
        resource.setFactory(factoryLoader.load(context, order));
        resource.setCalendar(calendarLoader.load(context, order));
        return resource;
    }

    /**
     * 排产过程锁，和解锁对应
     */
    public void lock(InvertedScheduleContext context, InvertedScheduleOrder order) {
        String key = context.getLock();
        if (!StringUtils.hasText(key)) {
            return;
        }
        String redisVal = order.getOrders().stream()
                .map(e -> e.getOrder().getConstructNo() + "_" + e.getOrder().getMaterName())
                .collect(Collectors.joining(","));
        ValueOperations<String, String> valOperation = stringRedisTemplate.opsForValue();
        if (Boolean.FALSE.equals(valOperation.setIfAbsent(key, redisVal, 1, TimeUnit.DAYS))) {
            throw SyExceptionUtils.e("{} 等订单排产中!", valOperation.get(key));
        }
    }


    /**
     * 解锁，和加锁对应
     */
    public void unlock(InvertedScheduleContext context) {
        String key = context.getLock();
        if (!StringUtils.hasText(key)) {
            return;
        }
        stringRedisTemplate.delete(key);
    }


    public void clearSchedule(InvertedScheduleOrder scheduleOrder) {
        for (DefaultOrder order : scheduleOrder.getOrders()) {
            apsWorkOrderMapper.deleteOrderId(order.getOrder().getId());
            apsWorkDetailMapper.deleteOrderId(order.getOrder().getId());
        }
    }

    public void saveSchedule(InvertedSchedule schedule) {
        saveBatch(schedule);
        saveWorkOrder(schedule);
        saveWorkDetail(schedule);
        saveShiftSchedule(schedule);
        for (DefaultOrderSchedule orderSchedule : schedule.getOrders()) {
            DefaultOrder order = orderSchedule.getOrder();
            LocalDateTime planStartDateTime = orderSchedule.getBomSchedules().stream()
                    .map(DefaultBomSchedule::getPlanStartDateTime)
                    .min(Comparator.naturalOrder())
                    .orElse(null);
            LocalDateTime planEndDateTime = orderSchedule.getBomSchedules().stream()
                    .map(DefaultBomSchedule::getPlanEndDateTime)
                    .max(Comparator.naturalOrder())
                    .orElse(null);
            orderMapper.lambdaUpdate()
                    .set(Order::getPlanStartDateTime, planStartDateTime)
                    .set(Order::getPlanEndDateTime, planEndDateTime)
                    .set(Order::getBatchId, schedule.getBatch().getId())
                    .set(Order::getOrderStatus, 5)
                    .eq(Order::getId, order.getOrder().getId())
                    .update();
        }
    }

    private void saveBatch(InvertedSchedule schedule) {
        LocalDateTime now = LocalDateTime.now();
        ApsBatch apsBatch = new ApsBatch();
        apsBatch.setBatchNo(now.format(DatePattern.NORM_DATETIME_FORMATTER));
        apsBatch.setSchedulingRule(2);
        apsBatch.setSchedulingDateTime(now);
        apsBatch.setStatus(2);
        apsBatchMapper.insert(apsBatch);
        schedule.setBatch(apsBatch);
    }

    private void saveWorkOrder(InvertedSchedule schedule) {
        // 按 bom 结构分层保存
        List<DefaultBomSchedule> bomSchedules = schedule.getOrders().stream()
                .flatMap(
                        e -> e.getBomSchedules().stream()
                                .filter(bom -> Objects.equals(bom.getBom().getBom().getLevel(), 1))
                )
                .collect(Collectors.toList());
        while (!bomSchedules.isEmpty()) {
            List<ApsWorkOrder> workOrders = new ArrayList<>();
            for (DefaultBomSchedule bomSchedule : bomSchedules) {
                Order order = bomSchedule.getBom().getOrder().getOrder();
                ApsOrder apsOrder = bomSchedule.getBom().getOrder().getApsOrders().get(0);
                OrderBom bom = bomSchedule.getBom().getBom();
                DefaultBomSchedule parent = bomSchedule.getParent();

                ApsWorkOrder workOrder = MkBeanUtils.copyProperties(bom, ApsWorkOrder.class);
                workOrder.setId(null);
                workOrder.setApsOrderId(apsOrder.getId());
                workOrder.setBomId(bom.getId());
                workOrder.setBomPid(bom.getPid());

                if (parent != null) {
                    workOrder.setPid(parent.getApsWorkOrder().getId());
                } else {
                    workOrder.setPid(null);
                }
                workOrder.setStatus(1);
                workOrder.setFinishCount(new BigDecimal(0));
                workOrder.setOrderType(order.getOrderType());
                workOrder.setOrderCount(bom.getTotalCount().multiply(new BigDecimal(order.getOrderQty())));
                workOrder.setPlanStartDateTime(bomSchedule.getPlanStartDateTime());
                workOrder.setPlanEndDateTime(bomSchedule.getPlanEndDateTime());
                bomSchedule.setApsWorkOrder(workOrder);
                workOrders.add(workOrder);
            }
            apsWorkOrderMapper.insertBatch(workOrders, 2000);

            bomSchedules = bomSchedules.stream()
                    .map(DefaultBomSchedule::getChildren)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toList());
        }
    }

    private void saveWorkDetail(InvertedSchedule schedule) {
        List<ApsWorkDetail> workDetails = schedule.getOrders().stream()
                .map(DefaultOrderSchedule::getBomSchedules)
                .flatMap(Collection::stream)
                .map(DefaultBomSchedule::getPhaseSchedules)
                .flatMap(Collection::stream)
                .map(phaseSchedule -> {
                    OrderBom bom = phaseSchedule.getBomSchedule().getBom().getBom();
                    OrderPhase orderPhase = phaseSchedule.getPhase().getPhase();
                    ApsWorkOrder workOrder = phaseSchedule.getBomSchedule().getApsWorkOrder();

                    ApsWorkDetail workDetail = new ApsWorkDetail();
                    workDetail.setOrderId(workOrder.getOrderId());
                    workDetail.setApsOrderId(workOrder.getApsOrderId());
                    workDetail.setPhaseId(orderPhase.getId());
                    workDetail.setPlmId(workOrder.getPlmId());
                    workDetail.setApsWorkOrderId(workOrder.getId());
                    workDetail.setApsWorkOrderPid(workOrder.getPid());
                    workDetail.setStationId(phaseSchedule.getStation().getId());
                    workDetail.setGroupId(phaseSchedule.getGroup().getId());
                    workDetail.setWorkTime(orderPhase.getWorkTime());
                    workDetail.setTotalWorkTime((int) phaseSchedule.getExecuteTime().getSeconds());
                    workDetail.setEnterTime(phaseSchedule.getStation().getEnterTime());
                    workDetail.setOutTime(phaseSchedule.getStation().getOutTime());
                    workDetail.setChangTime(phaseSchedule.getStation().getChangTime());
                    workDetail.setPhaseSeq(orderPhase.getPhaseSeq());
                    workDetail.setPhaseName(orderPhase.getPhaseName());
                    workDetail.setPhaseCode(orderPhase.getPhaseCode());
                    workDetail.setMaterNo(orderPhase.getMaterNo());
                    workDetail.setMaterName(orderPhase.getMaterName());
                    workDetail.setGroupCode(orderPhase.getWorkGroupNumber());
                    workDetail.setPartCount(bom.getPartCount());
                    workDetail.setTotalCount(phaseSchedule.getTotalCount());
                    workDetail.setProcessCount(new BigDecimal(0));
                    workDetail.setFinishCount(new BigDecimal(0));
                    workDetail.setStatus(1);
                    workDetail.setDetailSeq(1);
                    workDetail.setPlanStartDateTime(phaseSchedule.getPlanStartDateTime());
                    workDetail.setPlanEndDateTime(phaseSchedule.getPlanEndDateTime());
                    workDetail.setReverseDetailId(null);
                    workDetail.setOrderType(workOrder.getOrderType());
                    workDetail.setType(2);
                    workDetail.setOp(orderPhase.getOp());
                    workDetail.setOpCode(orderPhase.getOpCode());
                    phaseSchedule.setWorkDetail(workDetail);
                    return workDetail;
                })
                .collect(Collectors.toList());
        apsWorkDetailMapper.insertBatch(workDetails, 2000);
    }

    private void saveShiftSchedule(InvertedSchedule schedule) {
        List<ApsDetailWeekday> shiftDetails = schedule.getOrders().stream()
                . map(DefaultOrderSchedule::getBomSchedules)
                .flatMap(Collection::stream)
                .map(DefaultBomSchedule::getPhaseSchedules)
                .flatMap(Collection::stream)
                .map(DefaultPhaseSchedule::getShiftSchedules)
                .flatMap(Collection::stream)
                .map(shiftSchedule -> {
                    OrderBom bom = shiftSchedule.getPhaseSchedule().getBomSchedule().getBom().getBom();
                    ApsWorkDetail workDetail = shiftSchedule.getPhaseSchedule().getWorkDetail();

                    ApsDetailWeekday calendar = new ApsDetailWeekday();
                    calendar.setOrderId(workDetail.getOrderId());
                    calendar.setApsOrderId(workDetail.getApsOrderId());
                    calendar.setApsBomId(bom.getId());
                    calendar.setPhaseId(workDetail.getPhaseId());
                    calendar.setPhaseCode(workDetail.getPhaseCode());
                    calendar.setPhaseName(workDetail.getPhaseName());
                    calendar.setApsWorkOrderId(workDetail.getApsWorkOrderId());
                    calendar.setApsWorkDetailId(workDetail.getId());
                    calendar.setGroupId(workDetail.getGroupId());
                    calendar.setStationGroupId(workDetail.getStationGroupId());
                    calendar.setStationId(workDetail.getStationId());
                    calendar.setWorkTime((int) shiftSchedule.getExecuteTime().getSeconds());
                    calendar.setPlanStartDateTime(shiftSchedule.getPlanStartDateTime());
                    calendar.setPlanEndDateTime(shiftSchedule.getPlanEndDateTime());
                    calendar.setShiftId(shiftSchedule.getShift().getId());
                    calendar.setShiftSeq(shiftSchedule.getShift().getSeq());
                    calendar.setShiftStartDateTime(shiftSchedule.getDate().minusDays(shiftSchedule.getShift().getOffsetStartDay()).atTime(shiftSchedule.getShift().getStartTime()));
                    calendar.setShiftEndDateTime(shiftSchedule.getDate().plusDays(shiftSchedule.getShift().getOffsetEndDay()).atTime(shiftSchedule.getShift().getEndTime()));
                    calendar.setShiftDay(shiftSchedule.getDate());
                    calendar.setState(1); // 3 已排产
                    calendar.setOrderType(workDetail.getOrderType());

                    return calendar;
                })
                .collect(Collectors.toList());

        apsDetailWeekdayMapper.insertBatch(shiftDetails, 2000);

    }

}
